abstract class Instrument {
  public int top, left, wide, high;
  color SKY, EARTH, RED, YELLOW, DULL_YELLOW, PALE_YELLOW, GREEN, BLUE, BLACK, WHITE;
  
  public Instrument() {
    SKY = color(0,125,250);
    EARTH = color(200,100,0);
    RED = color(255,0,0);
    YELLOW = color(255,255,0);
    DULL_YELLOW = color(200,200,0);
    PALE_YELLOW = color(200,200,100);
    GREEN = color(0,255,0);
    BLUE = color(0,0,255);
    BLACK = color(0,0,0);
    WHITE = color(255,255,255);
  }
    
  public void setRect(int[] rect) {
    left = rect[0];
    top = rect[1];
    wide = rect[2];
    high = rect[3];
  }

  
  public void draw(float value) {
  }
  
  public void draw(float xVal, float yVal) {
  }

  
}

abstract class Gauge extends Instrument {
  public float minVal, maxVal;
  public color baseColor, backColor;
  public IntList controlColor;
  public FloatList controlValue;
  
  public Gauge(){
    controlColor = new IntList();
    controlValue = new FloatList();
  }
  
  public Gauge(int[] padRect, float minV, float maxV) {
    controlColor = new IntList();
    controlValue = new FloatList();
    baseColor = WHITE;
    backColor = BLACK;
    setRect(padRect);
    minVal = minV;
    maxVal = maxV;
  }

    
  public void setColor(color c, color b) {
    baseColor = c;
    backColor = b;
  }
  
  public void setColor(IntList c, FloatList val) {
    for (int ctr = 0; ctr < c.size(); ctr++) {
      setColor(c.get(ctr), val.get(ctr));
    }
  }
  
  public void setColor(color c, float val) {
    color tempColor = c;
    float tempVal = val;
    if (val < minVal) {
      tempColor = baseColor;
      tempVal = minVal;
      baseColor = c;
      minVal = val;
    }

    for (int ctr = 0; ctr < controlColor.size(); ctr++) {
      if (tempVal < controlValue.get(ctr)) {
        color tc = controlColor.get(ctr);
        float tv = controlValue.get(ctr);
        controlColor.set(ctr, tempColor);
        controlValue.set(ctr, tempVal);
        tempColor = tc;
        tempVal = tv;
      }
    }
    
    controlColor.append(tempColor);
    controlValue.append(tempVal);
  }
    
  public color dimFill(color c) {
    return lerpColor(backColor, c, 0.20);
  }

  public color formatColor(float value) {
    color theColor = baseColor;
    if (controlColor.size() > 0) {
      for (int ctr = 0; ctr < controlColor.size(); ctr++) {
        if (value >= controlValue.get(ctr)) theColor = controlColor.get(ctr);
      }
    }
    return theColor;
  }
  
}

public class Vslider extends Gauge {
  
  public Vslider(int[] padRect, float minV, float maxV) {
    super(padRect, minV, maxV);
  }
  
  
  public void draw(float value) {
    strokeWeight(2);
    fill(backColor);
    color theColor = formatColor(value);
    stroke(theColor);
    rect(left, top, wide, high);  // yellow outline
    noStroke();
    float dy = high/30;
    float v = constrain((value - minVal) / (maxVal - minVal), 0.0, 1.0);
    float y = (1.0 - v) * high - dy;
    fill(dimFill(theColor));
    rect(left+1, top + y + 1, wide-2, high - y - 1);
    fill(theColor);
    rect(left, top + y, wide, 2*dy);
  }
}

public class Hslider extends Gauge {
  
  public Hslider(int[] padRect, float minV, float maxV) {
    super(padRect, minV, maxV);
  }

  public void draw(float value) {
    color theColor = formatColor(value);
    strokeWeight(2);
    fill(backColor);
    stroke(theColor);
    rect(left, top, wide, high);  // yellow outline
    noStroke();
    float dx =wide/30;
    float v = constrain((value - minVal) / (maxVal - minVal), 0.0, 1.0);
    float x = v * wide - dx;
    fill(dimFill(theColor));
    rect(left + 1, top + 1, x + left, high - 2);
    fill(theColor);
    rect(left + x, top, 2*dx, high);
  }
}

public class XYpad extends Gauge {
  
  float minY, maxY;
  int colorMode;
  
  public XYpad(int[] padRect, float minV, float maxV) {
    super(padRect, minV, maxV);
    minY = maxV;
    maxY = minV;
    colorMode = 0;
  }
  
  public XYpad(int[] padRect, float minV, float maxV, int mode) {
    super(padRect, minV, maxV);
    minY = maxV;
    maxY = minV;
    colorMode = mode;
  }
  
  public XYpad(int[] padRect, float[] minV, float[] maxV) {
    super(padRect, minV[0], maxV[0]);
    minY = maxV[1];
    maxY = minV[1];
    colorMode = 0;
  }

  public XYpad(int[] padRect, float[] minV, float[] maxV, int mode) {
    super(padRect, minV[0], maxV[0]);
    minY = maxV[1];
    maxY = minV[1];
    colorMode = mode;
  }
  
  public color formatColor(float xVal, float yVal) {
    color c = baseColor;
    switch (colorMode) {
      case 0:
      {
        c = formatColor(xVal);
      }
      case 1:
      {
        c = formatColor(yVal);
      }
      case 2:
      {
        c = formatColor(sqrt((xVal * xVal) + (yVal * yVal)));
      }
    }
    return c;
  }
  
  public void draw(float xPad, float yPad){
    color theColor = formatColor(xPad, yPad);
    strokeWeight(2);
    fill(dimFill(theColor));
    stroke(theColor);
    rect(left, top, wide, high);  // yellow outline
    noStroke();
    int dx = (wide/20);
    int dy = (high/20);
    float xP = constrain((xPad - minVal) / (maxVal - minVal), 0.0, 1.0);
    float yP = constrain((yPad - minY) / (maxY - minY), 0.0, 1.0);
    int x1 =  int(xP * wide - dx) + left;
    int y1 =  int(yP * high - dy) + top;
    fill(theColor);
    rect( x1, y1, dx*2, dy*2);  // draw square
    stroke(theColor);
    strokeWeight(1);
    line(x1 + dx, top, x1 + dx, top + high);
    line(left, y1 + dy, left + wide, y1 + dy);
  }
}

public class XYvector extends XYpad {
  public XYvector(int[] padRect, float minV, float maxV) {
    super(padRect, minV, maxV);
  }
  
  public XYvector(int[] padRect, float minV, float maxV, int mode) {
    super(padRect, minV, maxV, mode);
  }
  
  public XYvector(int[] padRect, float[] minV, float[] maxV) {
    super(padRect, minV, maxV);
  }

  public XYvector(int[] padRect, float[] minV, float[] maxV, int mode) {
    super(padRect, minV, maxV, mode);
  }
    
  public void draw(float xPad, float yPad){
    strokeWeight(2);
    fill(dimFill(baseColor));
    stroke(baseColor);
    rect(left, top, wide, high);  // yellow outline
    strokeWeight(1);
    int x0 = wide/2 + left;
    int y0 = high/2 + top;
    line(x0, top, x0, top + high);
    line(left, y0, left + wide, y0);
    color theColor = formatColor(xPad, yPad);
    addVector(xPad, yPad, theColor);
  }
  
  public void addVector(float x, float y, color c) {
    float xP = constrain((x - minVal) / (maxVal - minVal), 0.0, 1.0);
    float yP = constrain((y - minY) / (maxY - minY), 0.0, 1.0);
    int x1 =  int(xP * wide) + left;
    int y1 =  int(yP * high) + top;
    int x0 = wide/2 + left;
    int y0 = high/2 + top;
    strokeWeight(3);
    stroke(c);
    line(x0, y0, x1, y1);  // draw vector
  }
  
}

public class Rotary extends Gauge {
  
  public Rotary(int[] padRect, float minV, float maxV) {
    super(padRect, minV, maxV);
  }
  
  void draw(float value) {
    color theColor = formatColor(value);
    strokeWeight(1);
    fill(dimFill(theColor));
    stroke(theColor);
    ellipseMode(CENTER);
    float xc = left + wide/2;
    float yc = top + high/2;
    arc(xc, yc, wide, high, -2.6-PI/2, 2.6-PI/2);
    float vv = constrain((value - minVal) / (maxVal - minVal), 0.0, 1.0);
    float v = (vv - 0.5) * 5.2 - PI/2;
    fill(theColor);
    arc(xc, yc, wide, high, v - 0.1, v + 0.1);
    fill(backColor);
    stroke(backColor);
    ellipse(xc, yc, wide/2, high/2);
  }

}

public class Compass extends Instrument {
  
  public Compass(int[] cRect) {
    setRect(cRect);
  }
  
  void draw(float heading) {
    strokeWeight(2);
    fill(BLACK);
    stroke(WHITE);
    ellipseMode(CENTER);
    float xc = float(left) + float(wide)/2;
    float yc = float(top) + float(high)/2;
    ellipse(xc, yc, wide, high);
    noStroke();
    fill(WHITE);
    float sin_h = sin(heading);
    float cos_h = cos(heading);
    float x1t = 0;
    float y1t = -float(high) * 0.47;
    float x2t =  (float(wide) * 0.1);
    float y2t = -(float(high) * 0.3);
    float x3t = -(float(wide) * 0.1);
    float y3t = -(float(high) * 0.3);
    float x1 = xc + x1t * cos_h + y1t * sin_h;
    float y1 = yc + y1t * cos_h - x1t * sin_h;
    float x2 = xc + x2t * cos_h + y2t * sin_h;
    float y2 = yc + y2t * cos_h - x2t * sin_h;
    float x3 = xc + x3t * cos_h + y3t * sin_h;
    float y3 = yc + y3t * cos_h - x3t * sin_h;
    triangle(x1,y1,x2,y2,x3,y3);
    x1t = float(high) * 0.3;
    x2t = float(high) * 0.45;
    x1 = xc + x1t * cos_h;
    y1 = yc - x1t * sin_h;
    x2 = xc + x2t * cos_h;
    y2 = yc - x2t * sin_h;
    strokeWeight(2);
    stroke(255);
    line(x1,y1,x2,y2);
    x1t = -x1t;
    x2t = -x2t;
    x1 = xc + x1t * cos_h;
    y1 = yc - x1t * sin_h;
    x2 = xc + x2t * cos_h;
    y2 = yc - x2t * sin_h;
    line(x1,y1,x2,y2);
    y1t = -x1t;
    y2t = -x2t;
    x1 = xc + y1t * sin_h;
    y1 = yc + y1t * cos_h;
    x2 = xc + y2t * sin_h;
    y2 = yc + y2t * cos_h;
    line(x1,y1,x2,y2);
    float SIN45 = sqrt(2)/2;
    x1t = float(high) * 0.4 * SIN45;
    x2t = float(high) * 0.425 * SIN45;
    y1t = x1t;
    y2t = x1t;
    x1 = xc + x1t * cos_h + y1t * sin_h;
    y1 = yc + y1t * cos_h - x1t * sin_h;
    x2 = xc + x2t * cos_h + y2t * sin_h;
    y2 = yc + y2t * cos_h - x2t * sin_h;
    line(x1,y1,x2,y2);
    x1t = -x1t;
    x2t = -x2t;
    x1 = xc + x1t * cos_h + y1t * sin_h;
    y1 = yc + y1t * cos_h - x1t * sin_h;
    x2 = xc + x2t * cos_h + y2t * sin_h;
    y2 = yc + y2t * cos_h - x2t * sin_h;
    line(x1,y1,x2,y2);
    y1t = -y1t;
    y2t = -y2t;
    x1 = xc + x1t * cos_h + y1t * sin_h;
    y1 = yc + y1t * cos_h - x1t * sin_h;
    x2 = xc + x2t * cos_h + y2t * sin_h;
    y2 = yc + y2t * cos_h - x2t * sin_h;
    line(x1,y1,x2,y2);
    x1t = -x1t;
    x2t = -x2t;
    x1 = xc + x1t * cos_h + y1t * sin_h;
    y1 = yc + y1t * cos_h - x1t * sin_h;
    x2 = xc + x2t * cos_h + y2t * sin_h;
    y2 = yc + y2t * cos_h - x2t * sin_h;
    line(x1,y1,x2,y2);
  }
}

public class Horizon extends Instrument {
  
  public Horizon(int[] padRect) {
    setRect(padRect);
  }

  void draw(float pitch, float roll) {
    
    pitch = pitch % (2 * PI);
    if (pitch > PI) { 
      pitch = pitch - (2 * PI); 
    } else if (pitch < -PI) {
      pitch = pitch + (2 * PI);
    }
    if (pitch > (PI/2)) {
      pitch = PI - pitch;
      roll = PI - roll;
    } else if (pitch < (-PI/2)) {
      pitch = -PI - pitch;
      roll = PI - roll;
    }
    
    strokeWeight(2);
    fill(SKY);
    stroke(WHITE);
    ellipseMode(CENTER);
    float xc = float(left) + float(wide)/2;
    float yc = float(top) + float(high)/2;
    ellipse(xc, yc, wide, high);
    fill(EARTH);
    float theta1 = pitch - roll;
    float theta2 = PI - pitch - roll;
    arc(xc, yc, wide, high, theta1, theta2);
    float x1 = xc + wide * 0.5 * cos(theta1);
    float y1 = yc + high * 0.5 * sin(theta1);
    float x2 = xc + wide * 0.5 * cos(theta2);
    float y2 = yc + high * 0.5 * sin(theta2);
    line(x1,y1,x2,y2);
    noStroke();
    if (pitch > 0) {
      fill(SKY);
    } else {
      fill(EARTH);
    }
    triangle(xc, yc, x1, y1, x2, y2);
    
    strokeWeight(3);
    stroke(DULL_YELLOW);
    fill(DULL_YELLOW);
    x1 = wide * 0.07;
    x2 = wide * 0.2;
    line(xc + x1, yc, xc + x2, yc);
    line(xc - x1, yc, xc - x2, yc);
    noStroke();
    ellipse(xc, yc, 3, 3);
    
  }
}

abstract class Dial extends Instrument {
  PImage face;
  
  public Dial(int[] padRect, String fname) {
    setRect(padRect);
    face = loadImage(fname);
  }
  
  public class Indicator {
    float pointerLength, pointerWidth;
    color iColor;
    
    public Indicator(float len, float wid, color c) {
      pointerLength = len;
      pointerWidth = wid;
      iColor = c;
    }
    
    public void draw(float angle) {
      float sin_t = sin(angle);
      float cos_t = cos(angle);
      float cx = left + wide * 0.5;
      float cy = top + high * 0.5;
      float y1 = -wide * pointerLength / 2.0;
      float xt = wide * pointerWidth / 2.0;
      float yt = y1 + (xt * 1.5);
      float x1 = -sin_t * y1;
      y1 = cos_t * y1;
      float y2 = cos_t * yt + sin_t * xt;
      float x2 = cos_t * xt - sin_t * yt;
      float y5 = cos_t * yt + sin_t * -xt;
      float x5 = cos_t * -xt - sin_t * yt;
      float y3 = cos_t * xt + sin_t * xt;
      float x3 = cos_t * xt - sin_t * xt;
      float y4 = cos_t * xt + sin_t * -xt;
      float x4 = cos_t * -xt - sin_t * xt;
      fill(iColor);
      noStroke();
      beginShape();
      vertex(cx + x1, cy + y1);
      vertex(cx + x2, cy + y2);
      vertex(cx + x3, cy + y3);
      vertex(cx + x4, cy + y4);
      vertex(cx + x5, cy + y5);
      endShape(CLOSE);
    }
  }
}

class Altimeter extends Dial {
  private Indicator fIndicator, tIndicator, ttIndicator;
  private boolean displayAGL;
  
  public Altimeter(int[] padRect) {
    super(padRect, "altFace.jpg");
    fIndicator = new Indicator(0.8, 0.03, WHITE);
    tIndicator = new Indicator(0.4, 0.075, WHITE);
    ttIndicator = new Indicator(0.95, 0.01, WHITE);
    displayAGL = false;
  }
  
  public void setAGL(boolean b) {
    displayAGL = b;
  }
  
  public void draw(float altitude) {
    image(face, left, top, wide, high);
    
    strokeWeight(2);
    noFill();
    stroke(WHITE);
    ellipseMode(CORNER);
    ellipse(left, top, wide, high);
    
    float feet, t_feet, tt_feet;
    
    feet = altitude % 1000.0;
    t_feet = ((altitude)/1000) % 10;
    tt_feet = ((altitude)/10000) % 10;
    
    if (displayAGL) {
      feet = 2 * PI * (altitude % 100) / 100;
      t_feet = 2 * PI * ((altitude / 100) % 10) / 10;
      tt_feet = 2 * PI * ((altitude / 1000) % 10) / 10;
    } else {
      feet = 2 * PI * (altitude % 1000) / 1000;
      t_feet = 2 * PI * ((altitude / 1000) % 10) / 10;
      tt_feet = 2 * PI * ((altitude / 10000) % 10) / 10;
    }
    
    fIndicator.draw(feet);
    tIndicator.draw(t_feet);
    ttIndicator.draw(tt_feet);
  }
  
}

class TurnCoord extends Instrument {
  
  public TurnCoord(int[] padRect) {
    setRect(padRect);
  }

  void draw(float yawPlusRoll, float yaw_accel) {
    strokeWeight(2);
    fill(BLACK);
    stroke(WHITE);
    ellipseMode(CENTER);
    float cx = float(left) + float(wide)*0.5;
    float cy = float(top) + float(high)*0.5;
    ellipse(cx, cy, wide, high);
    float rx = wide * 0.5;
    float ry = high * 0.5;
    float x1 = 0.5 * rx;
    float x2 = 0.3 * rx;
    float y1 = 0.25 * ry;
    float y2 = 0.3 * ry;
    noStroke();
    fill(PALE_YELLOW);
    rect(cx - x1, cy + y1, 2 * x1, y2);
    strokeWeight(1);
    stroke(BLACK);
    x1 = 0.17 * rx;
    line(cx + x1, cy + y1, cx + x1, cy + y1 + y2);
    line(cx - x1, cy + y1, cx - x1, cy + y1 + y2);
    y1 = 0.4 * ry;
    x1 = 0.35 * rx * constrain(yaw_accel * 5, -1, 1);
    noStroke();
    fill(BLACK);
    ellipse(cx + x1, cy + y1, x2, y2);
    
    strokeWeight(2);
    stroke(WHITE);
    x1 = 0.8 * rx;
    line(cx + x1, cy, cx + rx, cy);
    line(cx - x1, cy, cx - rx, cy);
    x1 = 0.762 * rx;
    x2 = 0.954 * rx;
    y1 = 0.24 * ry;
    y2 = 0.3 * ry;
    line(cx + x1, cy + y1, cx + x2, cy + y2);
    line(cx - x1, cy + y1, cx - x2, cy + y2);
    
    float theta = 5.82 * yawPlusRoll;
    float sin_t = sin(theta);
    float cos_t = cos(theta);
    x1 = 0.7 * rx * cos_t;
    y1 = 0.7 * rx * sin_t;
    line(cx - x1, cy - y1, cx + x1, cy + y1);
    x1 = 0.2 * rx * cos_t + 0.1 * ry * sin_t;
    y1 = -0.1 * ry * cos_t + 0.2 * rx * sin_t;
    x2 = -0.2 * rx * cos_t + 0.1 * ry * sin_t;
    y2 = -0.1 * ry * cos_t - 0.2 * rx * sin_t;
    line(cx + x1, cy + y1, cx + x2, cy + y2);
    x1 = 0.3 * ry * sin_t;
    y1 = 0.3 * ry * cos_t;
    line(cx, cy, cx + x1, cy - y1);
    noStroke();
    fill(WHITE);
    ellipse(cx, cy, 0.2 * rx, 0.2 * ry);
  }
  
}

class Variometer extends Dial {
    Indicator vIndicator;
  
  public Variometer(int[] padRect) {
    super(padRect, "variFace.jpg");
    vIndicator = new Indicator(0.8, 0.05, WHITE);
  }
  
  void draw(float altRate) {
    
    image(face, left, top, wide, high);
    
    strokeWeight(2);
    noFill();
    stroke(WHITE);
    ellipseMode(CORNER);
    ellipse(left, top, wide, high);
    
    float theta = constrain(altRate / 2000 * 3, -3, 3) - (PI / 2);
    vIndicator.draw(theta);
  }
}

public class AirSpeed extends Dial {
  float maxSpeed;
  Indicator sIndicator;
  
  public AirSpeed(int[] padRect, float max_speed) {
    super(padRect, "speedFace.jpg");
    maxSpeed = max_speed;
    sIndicator = new Indicator(0.8, 0.05, WHITE);
  }

  public void draw(float speed) {
    image(face, left, top, wide, high);
    
    sIndicator.draw(3.0 * PI * speed / maxSpeed / 2.0);
  }
}

public class Clock extends Dial {
  public Indicator hourHand, minuteHand, secondHand;
  public boolean drawSecond;
  
  public Clock(int[] padRect) {
    super(padRect, "clockFace.jpg");
    hourHand = new Indicator(0.4, 0.04, WHITE);
    minuteHand = new Indicator(0.7, 0.04, WHITE);
    secondHand = new Indicator(0.7, 0.01, RED);
    drawSecond = false;
  }
  
  private void drawFace() {
    image(face, left, top, wide, high);
    strokeWeight(2);
    noFill();
    stroke(WHITE);
    ellipseMode(CORNER);
    ellipse(left, top, wide - 2, high - 2);
  }
  
  public void draw(float theTime) {
    // theTime is expressed as a fraction of a day
    drawFace();
    int hour = int(theTime * 24) % 12;
    int min = int(theTime * 1440) % 60;
    int sec = int(theTime * 86400) % 60;
    draw(hour, min, sec);
  }
  
  public void draw(float hour, float min) {
    drawSecond = false;
    draw(int(hour), int(min), 0);
  }
  
  public void draw(int hour, int min) {
    drawSecond = false;
    draw(hour, min, 0);
  }

  public void draw(int hour, int min, int sec) {
    drawFace();
    float hAngle = (float(hour) + float(min)/60) * PI / 6;
    float mAngle = (float(min) + float(sec)/60) * PI / 30;
    
    hourHand.draw(hAngle);
    minuteHand.draw(mAngle);
    if (drawSecond) {
      float sAngle = float(sec) * PI / 30;
      secondHand.draw(sAngle);
    }
  }
  
  public void drawNow() {
    draw(hour(), minute(), second());
  }
}
